
package org.muninn.niagara.bql;

import javax.baja.collection.BITable;
import javax.baja.collection.Column;
import javax.baja.collection.ColumnList;
import javax.baja.collection.TableCursor;
import javax.baja.naming.BOrd;
import javax.baja.status.BStatusNumeric;
import javax.baja.status.BStatusString;
import javax.baja.sys.Action;
import javax.baja.sys.BComponent;
import javax.baja.sys.BDouble;
import javax.baja.sys.BIcon;
import javax.baja.sys.BRelTime;
import javax.baja.sys.Clock;
import javax.baja.sys.Flags;
import javax.baja.sys.Property;
import javax.baja.sys.Sys;
import javax.baja.sys.Type;
import java.lang.Math;

/**
 * Aggregator for VAV statistical data. <code>executePeriod</code> should be set very high (longer period) to avoid overworking the JACE.
 * 
 * @author Conor Freeland, Muninn Group
 * @creation June 2012
 */
public class BVAVSampler extends BComponent
{
  //
  //Declare static properties
  //
  /** Time interval at which object executes BQL query and calculates values */
  public static final Property executePeriod = newProperty(Flags.SUMMARY,  BRelTime.make(60000));

  
  /** Query for zone temperature points (baja:StatusString) */
  public static final Property queryTemp = newProperty(Flags.SUMMARY,  new BStatusString());
  /** Query for zone temperature setpoints (baja:StatusString) */
  public static final Property querySetpt = newProperty(Flags.SUMMARY,  new BStatusString());
  /** Number/Count of VAVs accessed by zone temperature query (baja:StatusNumeric) */
  public static final Property numVAVs = newProperty(Flags.SUMMARY,  new BStatusNumeric());
  /** Average of values returned by zone temperature query (baja:StatusNumeric) */
  public static final Property avgTemp = newProperty(Flags.SUMMARY,  new BStatusNumeric());
  /** Minimum value returned by zone temperature query (baja:StatusNumeric) */
  public static final Property minTemp = newProperty(Flags.SUMMARY,  new BStatusNumeric());
  /** Maximum value returned by zone temperature query (baja:StatusNumeric) */
  public static final Property maxTemp = newProperty(Flags.SUMMARY,  new BStatusNumeric());
  /** Maximum difference between a zone temperature and its setpoint (baja:StatusNumeric) */
  public static final Property maxDiff = newProperty(Flags.SUMMARY,  new BStatusNumeric());

  
  /** Query for for discharge temperature points (baja:StatusString) */
  public static final Property queryDisc = newProperty(Flags.SUMMARY,  new BStatusString());
  /** Average of values returned by discharge temperature query (baja:StatusNumeric) */
  public static final Property avgDiscTemp = newProperty(Flags.SUMMARY,  new BStatusNumeric());
  /** Minimum value returned by discharge temperature query (baja:StatusNumeric) */
  public static final Property minDiscTemp = newProperty(Flags.SUMMARY,  new BStatusNumeric());
  /** Maximum value returned by discharge temperature query (baja:StatusNumeric) */
  public static final Property maxDiscTemp = newProperty(Flags.SUMMARY,  new BStatusNumeric());

  
  /** Query for for zone humidity points (baja:StatusString) */
  public static final Property queryHum = newProperty(Flags.SUMMARY,  new BStatusString());
  /** Number/Count of VAVs returned by zone humidity query (baja:StatusNumeric) */
  public static final Property numHVAVs = newProperty(Flags.SUMMARY,  new BStatusNumeric());
  /** Average of values returned by zone humidity query (baja:StatusNumeric) */
  public static final Property avgHum = newProperty(Flags.SUMMARY,  new BStatusNumeric());

  
  /** Query for for damper position points (baja:StatusString) */
  public static final Property queryDampOpen = newProperty(Flags.SUMMARY,  new BStatusString());
  /** Threshold for considering a damper to be fully open (baja:StatusNumeric) */
  public static final Property threshDampOpen = newProperty(Flags.SUMMARY, new BStatusNumeric());
  /** Average of values returned by damper position query (baja:StatusNumeric) */
  public static final Property avgDampOpen = newProperty(Flags.SUMMARY,  new BStatusNumeric());
  /** Minimum value returned by damper position query (baja:StatusNumeric) */
  public static final Property minDampOpen = newProperty(Flags.SUMMARY,  new BStatusNumeric());
  /** Maximum value returned by damper position query (baja:StatusNumeric) */
  public static final Property maxDampOpen = newProperty(Flags.SUMMARY,  new BStatusNumeric());
  
  
  /** Query for for airflow rate points (baja:StatusString) */
  public static final Property queryFlowRate = newProperty(Flags.SUMMARY,  new BStatusString());
  /** Threshold for considering a damper to be fully open (baja:StatusNumeric) */
  public static final Property threshFlowRate = newProperty(Flags.SUMMARY, new BStatusNumeric());
  /** Average of values returned by airflow rate query (baja:StatusNumeric) */
  public static final Property avgFlowRate = newProperty(Flags.SUMMARY,  new BStatusNumeric());
  /** Minimum value returned by airflow rate query (baja:StatusNumeric) */
  public static final Property minFlowRate = newProperty(Flags.SUMMARY,  new BStatusNumeric());
  /** Maximum value returned by airflow rate query (baja:StatusNumeric) */
  public static final Property maxFlowRate = newProperty(Flags.SUMMARY,  new BStatusNumeric());
  /** Total of values returned by airflow rate query (baja:StatusNumeric) */
  public static final Property totFlowRate = newProperty(Flags.SUMMARY, new BStatusNumeric());
  
  
  /** Number of VAVs with damper position above threshold, but airflow rate below threshold (baja:StatusNumeric) */
  public static final Property numStarved = newProperty(Flags.SUMMARY, new BStatusNumeric());
  /** Number of VAVs with damper position below threshold, but airflow rate above threshold (baja:StatusNumeric) */
  public static final Property numFlooded = newProperty(Flags.SUMMARY, new BStatusNumeric());
  
  
  /** Query for for heating enable points (baja:StatusString) */
  public static final Property queryHeat = newProperty(Flags.SUMMARY,  new BStatusString());
  /** Threshold for considering a heater to be fully activated */
  public static final Property threshHeat = newProperty(Flags.SUMMARY, new BStatusNumeric());
  /** Average of values returned by discharge temperature query (baja:StatusNumeric) */
  public static final Property avgHeat = newProperty(Flags.SUMMARY,  new BStatusNumeric());
  /** Minimum value returned by discharge temperature query (baja:StatusNumeric) */
  public static final Property minHeat = newProperty(Flags.SUMMARY,  new BStatusNumeric());
  /** Maximum value returned by discharge temperature query (baja:StatusNumeric) */
  public static final Property maxHeat = newProperty(Flags.SUMMARY,  new BStatusNumeric());
  /** Number of VAVs with heat above threshold (baja:StatusNumeric) */
  public static final Property numHeating = newProperty(Flags.SUMMARY,  new BStatusNumeric());

  
  /** Query for for CO2 level points (baja:StatusString) */
  public static final Property queryCO2 = newProperty(Flags.SUMMARY,  new BStatusString());
  /** Average of values returned by CO2 level query (baja:StatusNumeric) */
  public static final Property avgCO2Level = newProperty(Flags.SUMMARY,  new BStatusNumeric());
  /** Minimum value returned by CO2 level query (baja:StatusNumeric) */
  public static final Property minCO2Level = newProperty(Flags.SUMMARY,  new BStatusNumeric());
  /** Maximum value returned by CO2 level query (baja:StatusNumeric) */
  public static final Property maxCO2Level = newProperty(Flags.SUMMARY,  new BStatusNumeric());

  

  ////////////////////////////////////////////////////////////////
  // Getters
  ////////////////////////////////////////////////////////////////

  public BRelTime getExecutePeriod() { return (BRelTime)get("executePeriod"); }

  public BStatusString getQueryTemp() { return (BStatusString)get("queryTemp"); }
  public BStatusString getQuerySetpt() { return (BStatusString)get("querySetpt"); }
  public BStatusNumeric getNumVAVs() { return (BStatusNumeric)get("numVAVs"); }
  public BStatusNumeric getAvgTemp() { return (BStatusNumeric)get("avgTemp"); }
  public BStatusNumeric getMinTemp() { return (BStatusNumeric)get("minTemp"); }
  public BStatusNumeric getMaxTemp() { return (BStatusNumeric)get("maxTemp"); }
  public BStatusNumeric getMaxDiff() { return (BStatusNumeric)get("maxDiff"); }

  public BStatusString getQueryDisc() { return (BStatusString)get("queryDisc"); }
  public BStatusNumeric getAvgDiscTemp() { return (BStatusNumeric)get("avgDiscTemp"); }
  public BStatusNumeric getMinDiscTemp() { return (BStatusNumeric)get("minDiscTemp"); }
  public BStatusNumeric getMaxDiscTemp() { return (BStatusNumeric)get("maxDiscTemp"); }

  public BStatusString getQueryHum() { return (BStatusString)get("queryHum"); }
  public BStatusNumeric getNumHVAVs() { return (BStatusNumeric)get("numHVAVs"); }
  public BStatusNumeric getAvgHum() { return (BStatusNumeric)get("avgHum"); }

  public BStatusString getQueryHeat() { return (BStatusString)get("queryHeat"); }
  public BStatusNumeric getThreshHeat() { return (BStatusNumeric)get("threshHeat"); }
  public BStatusNumeric getAvgHeat() { return (BStatusNumeric)get("avgHeat"); }
  public BStatusNumeric getMinHeat() { return (BStatusNumeric)get("minHeat"); }
  public BStatusNumeric getMaxHeat() { return (BStatusNumeric)get("maxHeat"); }
  public BStatusNumeric getNumHeating() { return (BStatusNumeric)get("numHeating"); }

  public BStatusString getQueryCO2() { return (BStatusString)get("queryCO2"); }
  public BStatusNumeric getAvgCO2Level() { return (BStatusNumeric)get("avgCO2Level"); }
  public BStatusNumeric getMinCO2Level() { return (BStatusNumeric)get("minCO2Level"); }
  public BStatusNumeric getMaxCO2Level() { return (BStatusNumeric)get("maxCO2Level"); }
  
  public BStatusString getQueryFlowRate() { return (BStatusString)get("queryFlowRate"); }
  public BStatusNumeric getThreshFlowRate() { return (BStatusNumeric)get("threshFlowRate"); }
  public BStatusNumeric getAvgFlowRate() { return (BStatusNumeric)get("avgFlowRate"); }
  public BStatusNumeric getMinFlowRate() { return (BStatusNumeric)get("minFlowRate"); }
  public BStatusNumeric getMaxFlowRate() { return (BStatusNumeric)get("maxFlowRate"); }
  public BStatusNumeric getTotFlowRate() { return (BStatusNumeric)get("totFlowRate"); }
  public BStatusString getQueryDampOpen() { return (BStatusString)get("queryDampOpen"); }
  public BStatusNumeric getThreshDampOpen() { return (BStatusNumeric)get("threshDampOpen"); }
  public BStatusNumeric getAvgDampOpen() { return (BStatusNumeric)get("avgDampOpen"); }
  public BStatusNumeric getMinDampOpen() { return (BStatusNumeric)get("minDampOpen"); }
  public BStatusNumeric getMaxDampOpen() { return (BStatusNumeric)get("maxDampOpen"); }
  public BStatusNumeric getNumStarved() { return (BStatusNumeric)get("numStarved"); }
  public BStatusNumeric getNumFlooded() { return (BStatusNumeric)get("numFlooded"); }

  ////////////////////////////////////////////////////////////////
  // Setters
  ////////////////////////////////////////////////////////////////

  public void setExecutePeriod(javax.baja.sys.BRelTime v) { set("executePeriod", v); }

  public void setQueryTemp(javax.baja.status.BStatusString v) { set("queryTemp", v); }
  public void setQuerySetpt(javax.baja.status.BStatusString v) { set("querySetpt", v); }
  public void setNumVAVs(javax.baja.status.BStatusNumeric v) { set("numVAVs", v); }
  public void setAvgTemp(javax.baja.status.BStatusNumeric v) { set("avgTemp", v); }
  public void setMinTemp(javax.baja.status.BStatusNumeric v) { set("minTemp", v); }
  public void setMaxTemp(javax.baja.status.BStatusNumeric v) { set("maxTemp", v); }
  public void setMaxDiff(javax.baja.status.BStatusNumeric v) { set("maxDiff", v); }

  public void setQueryDisc(javax.baja.status.BStatusString v) { set("queryDisc", v); }
  public void setAvgDiscTemp(javax.baja.status.BStatusNumeric v) { set("avgDiscTemp", v); }
  public void setMinDiscTemp(javax.baja.status.BStatusNumeric v) { set("minDiscTemp", v); }
  public void setMaxDiscTemp(javax.baja.status.BStatusNumeric v) { set("maxDiscTemp", v); }

  public void setQueryHum(javax.baja.status.BStatusString v) { set("queryHum", v); }
  public void setNumHVAVs(javax.baja.status.BStatusNumeric v) { set("numHVAVs", v); }
  public void setAvgHum(javax.baja.status.BStatusNumeric v) { set("avgHum", v); }
  
  public void setQueryHeat(javax.baja.status.BStatusString v) { set("queryHeat", v); }
  public void setThreshHeat(javax.baja.status.BStatusNumeric v) { set("threshHeat", v); }
  public void setAvgHeat(javax.baja.status.BStatusNumeric v) { set("avgHeat", v); }
  public void setMinHeat(javax.baja.status.BStatusNumeric v) { set("minHeat", v); }
  public void setMaxHeat(javax.baja.status.BStatusNumeric v) { set("maxHeat", v); }
  public void setNumHeating(javax.baja.status.BStatusNumeric v) { set("numHeating", v); }
  
  public void setQueryCO2(javax.baja.status.BStatusString v) { set("queryCO2", v); }
  public void setAvgCO2Level(javax.baja.status.BStatusNumeric v) { set("avgCO2Level", v); }
  public void setMinCO2Level(javax.baja.status.BStatusNumeric v) { set("minCO2Level", v); }
  public void setMaxCO2Level(javax.baja.status.BStatusNumeric v) { set("maxCO2Level", v); }

  public void setQueryFlowRate(javax.baja.status.BStatusString v) { set("queryFlowRate", v); }
  public void setThreshFlowRate(javax.baja.status.BStatusNumeric v) { set("threshFlowRate", v); }
  public void setAvgFlowRate(javax.baja.status.BStatusNumeric v) { set("avgFlowRate", v); }
  public void setMinFlowRate(javax.baja.status.BStatusNumeric v) { set("minFlowRate", v); }
  public void setMaxFlowRate(javax.baja.status.BStatusNumeric v) { set("maxFlowRate", v); }
  public void setTotFlowRate(javax.baja.status.BStatusNumeric v) { set("totFlowRate", v); }
  public void setQueryDampOpen(javax.baja.status.BStatusString v) { set("queryDampOpen", v); }
  public void setThreshDampOpen(javax.baja.status.BStatusNumeric v) { set("threshDampOpen", v); }
  public void setAvgDampOpen(javax.baja.status.BStatusNumeric v) { set("avgDampOpen", v); }
  public void setMinDampOpen(javax.baja.status.BStatusNumeric v) { set("minDampOpen", v); }
  public void setMaxDampOpen(javax.baja.status.BStatusNumeric v) { set("maxDampOpen", v); }
  public void setNumStarved(javax.baja.status.BStatusNumeric v) { set("numStarved", v); }
  public void setNumFlooded(javax.baja.status.BStatusNumeric v) { set("numFlooded", v); }

  //
  //events for the timer
  //
  /**
   * Slot for the <code>timerExpired</code> action.
   */
  public static final Action timerExpired = newAction(Flags.HIDDEN, null);

  /**
   * Invoke the <code>timerExpired</code> action.
   */
  public void timerExpired() { invoke(timerExpired, null, null); }



  //////////////////////////////////////////////////////////////
  // Program Source
  ////////////////////////////////////////////////////////////////

  // MinMaxAvgSum - Points Selected by BQL
  // Daniel Drury / 2007
  // Brian Collins / 2008 - added code to account for invalid NaN values on LonNetwork and calculate the sum of the columns
  // Conor Freeland / 2012 - modified code to be a polling engine for VAV boxes and allow for user-controlled queries

  public void started() throws Exception
  {
    updateTimer();
  }

  public void doTimerExpired() throws Exception  //renamed from onExecute
  {
    if (getQueryTemp().getValue() != "")
    {
      if (getQuerySetpt().getValue() != "")
        getTempStats(getQueryTemp().getValue(), getQuerySetpt().getValue());
      else
        getTempStats(getQueryTemp().getValue());
    }
    if (getQueryDisc().getValue() != "")
      getDiscTempStats(getQueryDisc().getValue());
    if (getQueryHum().getValue() != "")
      getHumStats(getQueryHum().getValue());
    if (getQueryHeat().getValue() != "")
      getHeatStats(getQueryHeat().getValue());
    if (getQueryCO2().getValue() != "")
      getCO2Stats(getQueryCO2().getValue());
    if (getQueryFlowRate().getValue() != "" && getQueryDampOpen().getValue() != "")
      getAirControlStats(getQueryFlowRate().getValue(), getQueryDampOpen().getValue());
  }

  ////////////////////////////////////////////////////////////////
  // Zone Temperature Statistics
  public void getTempStats(String tempOrd) throws Exception
  { 
    BStatusNumeric tempValue;

    double numVAVs = 0,
        avgTemp = 0,
        minTemp = 0,
        maxTemp = 0,
        first = 1;

    updateTimer();

    BITable tempResult = (BITable)BOrd.make(tempOrd).resolve(Sys.getStation()).get();   // execute bql into table
    
    ColumnList tempColumns = tempResult.getColumns();        // get table cols data
    
    TableCursor tc = (TableCursor)tempResult.cursor();    // setup table cursor

    while (tc.next())                                 // walk bql rows
    {
      Column tempValueColumn = tempColumns.get(0);           // data is in 1st col
      tempValue = (BStatusNumeric) tc.get(tempValueColumn);   // get BStatusNumeric from table
      if (BDouble.make(tempValue.getValue()) != BDouble.NaN &&
          BDouble.make(tempValue.getValue()) != BDouble.POSITIVE_INFINITY)
        
        if (tempValue.getStatus().isValid())             // get Status of point, check valid
        {
          if (first != 0)                            // 1st valid point, use it for everything
          {
            minTemp = tempValue.getValue();
            maxTemp = tempValue.getValue();
            avgTemp = tempValue.getValue();
            numVAVs = 1;                                   // Init number points found
            first = 0;
            //data.setValue(good);
          }
          else                                       // 2nd+ valid point, check min/max, and avg it
          {
            if (tempValue.getValue() < minTemp) minTemp = tempValue.getValue();
            if (tempValue.getValue() > maxTemp) maxTemp = tempValue.getValue();
            avgTemp += tempValue.getValue();
            numVAVs++;
          }
        }          
    }

    getNumVAVs().setValue(numVAVs);       // set number points found
    if (numVAVs > 0)                  // only use avg/min/max if something valid found
    {
      getAvgTemp().setValue((int) Math.round(avgTemp / numVAVs));
      getMinTemp().setValue((int) Math.round(minTemp));
      getMaxTemp().setValue((int) Math.round(maxTemp));
      getMaxDiff().setValue(0);
    }
    else                          // No Valid Return, Return Default
    {
      getAvgTemp().setValue(0);
      getMinTemp().setValue(0);
      getMaxTemp().setValue(0);
      getMaxTemp().setValue(0);
    }
  }
  
  public void getTempStats(String tempOrd, String setOrd) throws Exception
  { 
    BStatusNumeric tempValue;
    BStatusNumeric setpValue;

    double numVAVs = 0,
        avgTemp = 0,
        minTemp = 0,
        maxTemp = 0,
        maxDiff = 0,
        first = 1;

    updateTimer();

    BITable tempResult = (BITable)BOrd.make(tempOrd).resolve(Sys.getStation()).get();   // execute bql into table
    BITable setResult = (BITable)BOrd.make(setOrd).resolve(Sys.getStation()).get();
    
    ColumnList tempColumns = tempResult.getColumns();        // get table cols data
    ColumnList setColumns = setResult.getColumns();
    
    TableCursor tc = (TableCursor)tempResult.cursor();    // setup table cursor
    TableCursor sc = (TableCursor)setResult.cursor();

    while (tc.next() && sc.next())                                 // walk bql rows
    {
      Column tempValueColumn = tempColumns.get(0);           // data is in 1st col
      Column setValueColumn = setColumns.get(0);
      tempValue = (BStatusNumeric) tc.get(tempValueColumn);   // get BStatusNumeric from table
      setpValue = (BStatusNumeric) sc.get(setValueColumn);
      if (BDouble.make(tempValue.getValue()) != BDouble.NaN &&
          BDouble.make(tempValue.getValue()) != BDouble.POSITIVE_INFINITY &&
          BDouble.make(setpValue.getValue()) != BDouble.NaN &&
          BDouble.make(setpValue.getValue()) != BDouble.POSITIVE_INFINITY)
        
        if (tempValue.getStatus().isValid() && setpValue.getStatus().isValid())             // get Status of point, check valid
        {
          if (first != 0)                            // 1st valid point, use it for everything
          {
            minTemp = tempValue.getValue();
            maxTemp = tempValue.getValue();
            avgTemp = tempValue.getValue();
            maxDiff = tempValue.getValue() - setpValue.getValue();
            numVAVs = 1;                                   // Init number points found
            first = 0;
            //data.setValue(good);
          }
          else                                       // 2nd+ valid point, check min/max, and avg it
          {
            if (tempValue.getValue() < minTemp) minTemp = tempValue.getValue();
            if (tempValue.getValue() > maxTemp) maxTemp = tempValue.getValue();
            if (tempValue.getValue() - setpValue.getValue() > maxDiff) maxDiff = tempValue.getValue() - setpValue.getValue();
            avgTemp += tempValue.getValue();
            numVAVs++;
          }
        }          
    }

    getNumVAVs().setValue(numVAVs);       // set number points found
    if (numVAVs > 0)                  // only use avg/min/max if something valid found
    {
      getAvgTemp().setValue((int) Math.round(avgTemp / numVAVs));
      getMinTemp().setValue((int) Math.round(minTemp));
      getMaxTemp().setValue((int) Math.round(maxTemp));
      getMaxDiff().setValue((int) Math.round(maxDiff));
    }
    else                          // No Valid Return, Return Default
    {
      getAvgTemp().setValue(0);
      getMinTemp().setValue(0);
      getMaxTemp().setValue(0);
      getMaxTemp().setValue(0);
    }
  }
  ////////////////////////////////////////////////////////////////

  ////////////////////////////////////////////////////////////////
  // Discharge Temperature Statistics
  public void getDiscTempStats(String ord) throws Exception
  {
    BStatusNumeric Value;

    double numSampled = 0,
        avgDiscTemp = 0,
        minDiscTemp = 0,
        maxDiscTemp = 0,
        first = 1;

    updateTimer();

    BITable result = (BITable)BOrd.make(ord).resolve(Sys.getStation()).get();   // execute bql into table

    ColumnList columns = result.getColumns();        // get table cols data
    TableCursor c = (TableCursor)result.cursor();    // setup table cursor

    while (c.next())                                 // walk bql rows
    {
      Column valueColumn = columns.get(0);           // data is in 1st col
      Value = (BStatusNumeric) c.get(valueColumn);   // get BStatusNumeric from table
      if (BDouble.make(Value.getValue()) != BDouble.NaN &&
          BDouble.make(Value.getValue()) != BDouble.POSITIVE_INFINITY)
        
        if (Value.getStatus().isValid())             // get Status of point, check valid
        {
          if (first != 0)                            // 1st valid point, use it for everything
          {
            minDiscTemp = Value.getValue();
            maxDiscTemp = Value.getValue();
            avgDiscTemp = Value.getValue();
            numSampled = 1;                                   // Init number points found
            first = 0;
            //data.setValue(good);
          }
          else                                       // 2nd+ valid point, check min/max, and avg it
          {
            if (Value.getValue() < minDiscTemp) minDiscTemp = Value.getValue();
            if (Value.getValue() > maxDiscTemp) maxDiscTemp = Value.getValue();
            avgDiscTemp += Value.getValue();
            numSampled++;
          }
        }          
    }

    if (numSampled > 0)                  // only use avg/min/max if something valid found
    {
      getAvgDiscTemp().setValue((int) Math.round(avgDiscTemp / numSampled));
      getMinDiscTemp().setValue((int) Math.round(minDiscTemp));
      getMaxDiscTemp().setValue((int) Math.round(maxDiscTemp));
    }
    else                          // No Valid Return, Return Default
    {
      getAvgDiscTemp().setValue(0);
      getMinDiscTemp().setValue(0);
      getMaxDiscTemp().setValue(0);
    }
  }
  ////////////////////////////////////////////////////////////////

  ////////////////////////////////////////////////////////////////
  // Humidity Statistics
  public void getHumStats(String ord) throws Exception
  {
    BStatusNumeric Value;

    double numHVAVs = 0,
        avgHum = 0,
        first = 1;

    updateTimer();

    BITable result = (BITable)BOrd.make(ord).resolve(Sys.getStation()).get();   // execute bql into table

    ColumnList columns = result.getColumns();        // get table cols data
    TableCursor c = (TableCursor)result.cursor();    // setup table cursor

    while (c.next())                                 // walk bql rows
    {
      Column valueColumn = columns.get(0);           // data is in 1st col
      Value = (BStatusNumeric) c.get(valueColumn);   // get BStatusNumeric from table
      if (BDouble.make(Value.getValue()) != BDouble.NaN &&
          BDouble.make(Value.getValue()) != BDouble.POSITIVE_INFINITY)

        if (Value.getStatus().isValid())             // get Status of point, check valid
        {
          if (first != 0)                            // 1st valid point, use it for everything
          {
            avgHum = Value.getValue();
            numHVAVs = 1;                                   // Init number points found
            first = 0;
            //data.setValue(good);
          }
          else                                       // 2nd+ valid point, check min/max, and avg it
          {
            avgHum += Value.getValue();
            numHVAVs++;
          }
        }          
    }

    getNumHVAVs().setValue(numHVAVs);       // set number points found
    if (numHVAVs > 0) getAvgHum().setValue((int) Math.round(avgHum / numHVAVs));
    else getAvgHum().setValue(0);
  }
  ////////////////////////////////////////////////////////////////
 
  ////////////////////////////////////////////////////////////////
  // Heating Statistics
  public void getHeatStats(String ord) throws Exception
  {
    BStatusNumeric Value;

    double numSampled = 0,
           avgHeat = 0,
           minHeat = 0,
           maxHeat = 0;
    
    int numHeat = 0;

    updateTimer();

    BITable result = (BITable)BOrd.make(ord).resolve(Sys.getStation()).get();   // execute bql into table

    ColumnList columns = result.getColumns();        // get table cols data
    TableCursor c = (TableCursor)result.cursor();    // setup table cursor

    while (c.next())                                 // walk bql rows
    {
      Column valueColumn = columns.get(0);           // data is in 1st col
      Value = (BStatusNumeric) c.get(valueColumn);   // get BStatusBoolean from table
      if (BDouble.make(Value.getValue()) != BDouble.NaN &&
          BDouble.make(Value.getValue()) != BDouble.POSITIVE_INFINITY)
        
        if (Value.getStatus().isValid())             // get Status of point, check valid
        {
          if (Value.getValue() >= getThreshHeat().getValue()) numHeat ++;
          
          if (Value.getValue() > maxHeat) maxHeat = Value.getValue();
          if (Value.getValue() < minHeat) minHeat = Value.getValue();
          
          numSampled ++;
          avgHeat += Value.getValue();
        }          
    }

    if (numSampled > 0)                  // only use if something valid found
    {
      getAvgHeat().setValue((int) Math.round(avgHeat / numSampled));
      getMinHeat().setValue((int) Math.round(minHeat));
      getMaxHeat().setValue((int) Math.round(maxHeat));
      getNumHeating().setValue(numHeat);
    }
    else                          // No Valid Return, Return Default
    {
      getAvgHeat().setValue(0);
      getMinHeat().setValue(0);
      getMaxHeat().setValue(0);
      getNumHeating().setValue(0);
    }
  }
  ////////////////////////////////////////////////////////////////
  
  ////////////////////////////////////////////////////////////////
  // CO2 Statistics
  public void getCO2Stats(String ord) throws Exception
  {
    BStatusNumeric Value;

    double numSampled = 0,
        avgCO2Level = 0,
        minCO2Level = 0,
        maxCO2Level = 0,
        first = 1;

    updateTimer();

    BITable result = (BITable)BOrd.make(ord).resolve(Sys.getStation()).get();   // execute bql into table

    ColumnList columns = result.getColumns();        // get table cols data
    TableCursor c = (TableCursor)result.cursor();    // setup table cursor

    while (c.next())                                 // walk bql rows
    {
      Column valueColumn = columns.get(0);           // data is in 1st col
      Value = (BStatusNumeric) c.get(valueColumn);   // get BStatusNumeric from table
      if (BDouble.make(Value.getValue()) != BDouble.NaN &&
          BDouble.make(Value.getValue()) != BDouble.POSITIVE_INFINITY)
        
        if (Value.getStatus().isValid())             // get Status of point, check valid
        {
          if (first != 0)                            // 1st valid point, use it for everything
          {
            minCO2Level = Value.getValue();
            maxCO2Level = Value.getValue();
            avgCO2Level = Value.getValue();
            numSampled = 1;                                   // Init number points found
            first = 0;
            //data.setValue(good);
          }
          else                                       // 2nd+ valid point, check min/max, and avg it
          {
            if (Value.getValue() < minCO2Level) minCO2Level = Value.getValue();
            if (Value.getValue() > maxCO2Level) maxCO2Level = Value.getValue();
            avgCO2Level += Value.getValue();
            numSampled++;
          }
        }          
    }

    if (numSampled > 0)                  // only use avg/min/max if something valid found
    {
      getAvgCO2Level().setValue((int) Math.round(avgCO2Level / numSampled));
      getMinCO2Level().setValue((int) Math.round(minCO2Level));
      getMaxCO2Level().setValue((int) Math.round(maxCO2Level));
    }
    else                          // No Valid Return, Return Default
    {
      getAvgCO2Level().setValue(0);
      getMinCO2Level().setValue(0);
      getMaxCO2Level().setValue(0);
    }
  }
  ////////////////////////////////////////////////////////////////
  
  ////////////////////////////////////////////////////////////////
  // Damper/Airflow Statistics
  public void getAirControlStats(String airOrd, String dampOrd) throws Exception
  {
    BStatusNumeric airValue, dampValue;

    double avgflow = 0,
           avgopen = 0,
           minflow = 0,
           maxflow = 0,
           minopen = 0,
           maxopen = 0,
           numStarved = 0;
    
    int numFlooded = 0,
        numSampled = 0;

    updateTimer();

    BITable airResult = (BITable)BOrd.make(airOrd).resolve(Sys.getStation()).get();
    BITable dampResult = (BITable)BOrd.make(dampOrd).resolve(Sys.getStation()).get();

    ColumnList airColumns = airResult.getColumns();
    TableCursor airc = (TableCursor)airResult.cursor();

    ColumnList dampColumns = dampResult.getColumns();
    TableCursor dampc = (TableCursor)dampResult.cursor();

    while (airc.next() && dampc.next())                                 // walk bql rows
    {
      Column airValueColumn = airColumns.get(0);           // data is in 1st col
      Column dampValueColumn = dampColumns.get(0);

      airValue = (BStatusNumeric) airc.get(airValueColumn);   // get BStatusBoolean from table
      dampValue = (BStatusNumeric) dampc.get(dampValueColumn);
      if (BDouble.make(airValue.getValue()) != BDouble.NaN &&
          BDouble.make(airValue.getValue()) != BDouble.POSITIVE_INFINITY &&
          BDouble.make(dampValue.getValue()) != BDouble.NaN &&
          BDouble.make(dampValue.getValue()) != BDouble.POSITIVE_INFINITY)
        
        if (airValue.getStatus().isValid() && dampValue.getStatus().isValid())             // get Status of point, check valid
        {
          if (airValue.getValue() >= getThreshFlowRate().getValue() && dampValue.getValue() < getThreshDampOpen().getValue()) numFlooded++;
          else if (airValue.getValue() < getThreshFlowRate().getValue() && dampValue.getValue() >= getThreshDampOpen().getValue()) numStarved++;
          
          if (airValue.getValue() > maxflow) maxflow = airValue.getValue();
          if (airValue.getValue() < minflow) minflow = airValue.getValue();
          if (dampValue.getValue() > maxopen) maxopen = dampValue.getValue();
          if (dampValue.getValue() < minopen) minopen = dampValue.getValue();
          
          numSampled++;
          avgflow += airValue.getValue();
          avgopen += dampValue.getValue();
        }          
    }

    if (numSampled > 0)                  // only use if something valid found
    {
      getMaxFlowRate().setValue((int) Math.round(maxflow));
      getMinFlowRate().setValue((int) Math.round(minflow));
      getTotFlowRate().setValue((int) Math.round(avgflow));
      getMaxDampOpen().setValue((int) Math.round(maxopen));
      getMinDampOpen().setValue((int) Math.round(minopen));
      
      getAvgFlowRate().setValue((int) Math.round(avgflow / numSampled));
      getAvgDampOpen().setValue((int) Math.round(avgopen / numSampled));
      
      getNumStarved().setValue(numStarved);
      getNumFlooded().setValue(numFlooded);
    }
    else                          // No Valid Return, Return Default
    {
      getMaxFlowRate().setValue(0);
      getMinFlowRate().setValue(0);
      getTotFlowRate().setValue(0);
      getMaxDampOpen().setValue(0);
      getMinDampOpen().setValue(0);
      
      getAvgFlowRate().setValue(0);
      getAvgDampOpen().setValue(0);
      
      getNumStarved().setValue(0);
      getNumFlooded().setValue(0);
    }
  }
  ////////////////////////////////////////////////////////////////
  
  public void stopped() throws Exception
  {
    if (ticket != null) ticket.cancel();
  }
  
  
  
  void updateTimer()
  {            
    if (ticket != null) ticket.cancel();
    //ticket = Clock.schedule(getProgram(), getExecutePeriod(), BProgram.execute, null);
    ticket = Clock.schedulePeriodically(this, getExecutePeriod(), timerExpired, null);
  }    

  Clock.Ticket ticket;
  long lastOnExecuteTicks;

  public BIcon getIcon() { return icon; }
  private static final BIcon icon = BIcon.make("local:|module://muninn/org/muninn/niagara/graphics/MuninnGroup.PNG");

  //
  //TYPE declaration
  //
  public static final Type TYPE = Sys.loadType(BVAVSampler.class);
  public Type getType() { return TYPE; }
}